package org.mtmi.ui.controls;

import java.util.HashSet;
import java.util.Set;

import org.mtmi.io.IControlIO;
import org.mtmi.io.controls.RangeValueControlIO;
import org.mtmi.ui.GraphicUtil;
import org.mtmi.ui.controls.edits.AbstractRangeValueControlEditor;
import org.mtmi.ui.controls.listeners.ValueChangedListener;
import org.mtmi.ui.controls.listeners.ValueEvent;
import org.mtmi.ui.edit.IControlEditor;
import org.mtmi.ui.links.CID;
import org.mtmi.ui.links.IConnectable;
import org.mtmi.ui.links.IConnectionOutlinePointProvider;
import org.mtmi.ui.scenes.Scene;

/** 
 * Common part for control object controlling a value constrained into a range.
 *@see {@link AbstractSlider}, {@link AbstractKnob}
 */
public abstract class AbstractRangeValueControl extends AbstractValueControlledControl 
    implements IValueGenerator, ITouchableControl, IConnectable
{

	protected int valMax;
	protected int valMin;
	//note: value means valCurrent after
	
	private Set<ValueChangedListener> valueListeners;

	public AbstractRangeValueControl( Scene scene, int flags ) {
		super( scene, flags );
		valueListeners = new HashSet<ValueChangedListener>();
		//default values;
		valMin = 0;
		valMax = 100;
		setValue( 0 );
	}
	public AbstractRangeValueControl( Scene scene, int valCurrent, int valMin, int valMax, int flags ) {
		this( scene, flags );
		this.valMin = valMin;
		this.valMax = valMax;
		setValue( valCurrent ); //a choice...
	}
	
	/** @return normalized value: [0..1], by default if min==max return 0 */
	public float getValueF() {
		if( valMin==valMax ) return 0f;
		float k = (getValue()-valMin)/(float)(valMax-valMin);
		return k;
	}
	
	/** expand input value to value range, the call {@link #setValue(int)}
	 * @param f must be in range 0..1 otherwisze may have undesirable effects */
	public void setValueF( float f ) {
		int val = Math.round( (valMax-valMin)*f + valMin );
		setValue( val );
	}
	
	/** return maximum value allowed */
	public int getMaxValue() {
		return valMax;
	}
	
	/** change maximum allowed value */
	public void setMaxValue(int maxValue) {
		valMax = maxValue;
		setValue( Math.min( valMax, getValue() ) );
	}
	
	/** @return current minimum value */
	public int getMinValue() {
		return valMin;
	}
	
	/** change minimum allowed value */
	public void setMinValue(int minValue) {
		valMin = minValue;
		setValue( Math.max( valMin, getValue() ) );
	}
	
	/** Change the range of allowed values, current value is modified if outside new range */
	public void setRange( int vmin, int vmax ) {
		valMin = vmin;
		valMax = vmax;
		setValue( Math.max( valMin, Math.min( getValue(), valMax ) ) );
	}
	
	/** Change current value, fire notification  */
	@Override
	public void setValue( int v ) {
		if(v >= valMin && v <= valMax && v!=getValue() ) {
			super.setValue( v );
			notifyValueChanged();
		}
	}

	protected void notifyValueChanged() {
		if( valueListeners==null ) return;
		//don't want infinite loop if user link control in loop
		IValueGenerator ctr = getValueController();
		if( ctr!=null ) ctr.removeValueChangedListener( this );
		ValueEvent e = new ValueEvent( this, this, getValue() );
		ValueChangedListener list[] = valueListeners.toArray( new ValueChangedListener[0] ); 
		for ( ValueChangedListener l : list ) {
			l.valueChanged( e );
		}
		if( ctr!=null ) ctr.addValueChangedListener( this );
	}
	
	@Override
	public void addValueChangedListener( ValueChangedListener listener ) {
		valueListeners.add(listener);
	}
	
	@Override
	public void removeValueChangedListener( ValueChangedListener listener ) {
		valueListeners.remove(listener);
	}
	
	/** @return the list of listener currently listening value changed event */
	public ValueChangedListener[] getValueChangedListeners()
	{
		ValueChangedListener[] vl = valueListeners.toArray( new ValueChangedListener[0] );
		return vl;
	}
	
	@Override
	/** @return true if point is inside control bounds */
	public boolean containsTouch( TouchControlEvent e ) {
		return GraphicUtil.rectContains( location, size, e.x, e.y );
	}
	
	@Override
	public Object getAdapter( Class<?> clazz, Object arg ) {
		if( clazz == IValueGenerator.class ) { 
			return this;
		}
		if( clazz == IControlEditor.class ) {
			return new AbstractRangeValueControlEditor( this );
		}
		if( clazz == IControlIO.class ) {
			return new RangeValueControlIO();
		}
		return super.getAdapter( clazz, arg );
	}
	
	/** @return a string which can be used to get text extent, the maximum digit lengh etc,... */
	public static String worseDigitLength( int min, int max ) {
		int dcn = digitCount( min );
		int dcx = digitCount( max );
		int dc = Math.max( dcn, dcx );
		StringBuilder sb = new StringBuilder();
		for( int i=0; i<dc; i++ ) {
			sb.append('8');
		}
		return sb.toString();
	}
	
	/** @return the count of digit in given value, note that '-' sign is a digit */
	public static int digitCount( int value ) {
		int dc = 1;
		if( value < 0 ) {
			dc++;
			value = -value;
		}
		while( value >= 10 ) {
			dc++;
			value /=10;
		}
		return dc;
	}
	
	@Override
	protected void valueControllerValueChanged() {
		float k = getValueController().getValueF();
		int value = Math.round( (valMax-valMin)*k+valMin );
		setValue( value );
	}
	
	@Override
	/** @return {@link CID#VALUE_GENERATOR} */
	public String getConnectionAt( int x, int y ) {
		return CID.VALUE_GENERATOR;
	}
	
	@Override
	/** @return null by default */
	public IConnectionOutlinePointProvider getConnectionAnchor( String connection_id ) {
		return null;
	}
}
